import { libManager, SeedContext } from "../SeedApplication";
import { Action, App, Column, Group, Habtm, HasMany, HasOne, Seed, Stat, Element } from "../types";
import uaa from "../uaa";

export class Initializer {
  private app: App
  private seedContext: SeedContext
  private seedContexts: Record<string, SeedContext>

  constructor(app: App, seedContext: SeedContext, seedContexts: Record<string, SeedContext>) {
    this.app = app
    this.seedContext = seedContext
    this.seedContexts = seedContexts
  }

  public init() {
    this.initSeeds()
    this.initActions()
    this.initOther()
    this.initRelations()
    this.initName()
    this.initGroups()
  }

  private initName() {
    const app = this.app
    if(app.groups) {
      for(const groupName in app.groups) app.groups[groupName].name = groupName
    }
    if(app.homes) {
      for(const homeName in app.homes) app.homes[homeName].name = homeName
    }
    if(app.seeds) {
      for(const seedName in app.seeds) {
        const seed = app.seeds[seedName]
        seed.name = seedName

        const actions = seed.actions
        if(actions) for(const actionName in actions) actions[actionName].name = actionName

        const columns = seed.columns
        if(columns) for(const columnName in columns) columns[columnName].name = columnName

        const stats = seed.stats
        if(stats) for(const statName in stats) stats[statName].name = statName

        const groups = seed.groups
        if(groups) for(const groupName in groups) groups[groupName].name = groupName
      }
    }

    if(app.actions) {
      for(const actionName in app.actions) {
        const action = app.actions[actionName]
        action.name = actionName

        const stats = action.stats
        if(stats) for(const statName in stats) stats[statName].name = statName
      }
    }

    const micros = app.micros
    if(!micros) return
    
    for(const microName in micros) {
      const micro = micros[microName]
      micro.name = microName
      if(micro.homes) {
        for(const homeName in micro.homes) micro.homes[homeName].name = homeName
      }
      if(micro.seeds) {
        for(const seedName in micro.seeds) {
          const seed = micro.seeds[seedName]
          seed.name = seedName

          const actions = seed.actions
          if(actions) for(const actionName in actions) actions[actionName].name = actionName

          const columns = seed.columns
          if(columns) for(const columnName in columns) columns[columnName].name = columnName

          const stats = seed.stats
          if(stats) for(const statName in stats) stats[statName].name = statName

          const groups = seed.groups
          if(groups) for(const groupName in groups) groups[groupName].name = groupName
        }
      }
      if(micro.actions) {
        for(const actionName in micro.actions) {
          const action = micro.actions[actionName]
          action.name = actionName

          const stats = action.stats
          if(stats) for(const statName in stats) stats[statName].name = statName
        }
      }
    }
  }

  private initSeeds() {
    const libNames = this.app.libs || []
    const libSeeds = libManager.getLibSeeds(libNames, true)
    const seeds = this.mergeSeeds(this.app.seeds, libSeeds, this.seedContext?.seeds)
    if(seeds) this.app.seeds = seeds

    const micros = this.app.micros
    if(!micros) return
    
    for(const microName in micros) {
      const micro = micros[microName]
      const libNames = micro.libs || []
      const libSeeds = libManager.getLibSeeds(libNames)
      const seeds = this.mergeSeeds(micro.seeds, libSeeds, this.seedContext?.[microName]?.seeds)
      if(seeds) micro.seeds = seeds
    }
  }

  private initActions() {
    const libNames = this.app.libs || []
    const libActions = libManager.getLibActions(libNames)
    for(const actionName in uaa.actions) libActions[actionName] = uaa.actions[actionName]
    
    const actions = this.mergeActions(this.app.actions, libActions, this.seedContext?.actions)
    if(actions) this.app.actions = actions

    const micros = this.app.micros
    if(!micros) return
    
    for(const microName in micros) {
      const micro = micros[microName]
      const libNames = micro.libs || []
      const libActions = libManager.getLibActions(libNames)
      const actions = this.mergeActions(micro.actions, libActions, this.seedContexts?.[microName].actions)
      if(actions) micro.actions = actions
    }
  }

  private initOther() {
    if(this.app.seeds) {
      for(const seedName in this.app.seeds) {
        const seed = this.app.seeds[seedName]
        if(!seed.type) seed.type = 'module'
        const actions = seed.actions
        if(actions) {
          for(const actionName in actions) {
            const action = actions[actionName]
            // action.origin = 'seed'
            action.app = this.app
            action.seed = seed
          }
        }

        const columns = seed.columns
        if(columns) {
          for(const columnName in columns) {
            const column = columns[columnName]
            column.seed = seed
            column.app = this.app

            // if(column.type == 'code') column.required = true
          }
        }
      }
    }

    if(this.app.actions) {
      for(const actionName in this.app.actions) {
        const action = this.app.actions[actionName]
        // action.origin = 'app'
        action.app = this.app
      }
    }

    const micros = this.app.micros
    if(!micros) return

    for(const microName in micros) {
      const micro = micros[microName]
      if(micro.seeds) {
        for(const seedName in micro.seeds) {
          const seed = micro.seeds[seedName]
          if(!seed.type) seed.type = 'module'

          const actions = seed.actions
          if(actions) {
            for(const actionName in actions) {
              const action = actions[actionName]
              // action.origin = 'seed'
              action.app = this.app
              action.micro = micro
              action.seed = seed
            }
          }

          const columns = seed.columns
          if(columns) {
            for(const columnName in columns) {
              const column = columns[columnName]
              column.seed = seed
              column.app = this.app
              column.micro = micro

              // if(column.type == 'code') column.required = true
            }
          }
        }
      }

      if(micro.actions) {
        for(const actionName in micro.actions) {
          const action = micro.actions[actionName]
          // action.origin = 'micro'
          action.app = this.app
          action.micro = micro
        }
      }
    }
  }

  private initRelations() {
    if(this.app.seeds) {
      for(const seedName in this.app.seeds) {
        const seed = this.app.seeds[seedName]
        this.initSeedRelations(seed, this.app.seeds)
      }
    }

    const micros = this.app.micros
    if(!micros) return

    for(const microName in micros) {
      const micro = micros[microName]
      if(micro.seeds) {
        for(const seedName in micro.seeds) {
          const seed = micro.seeds[seedName]
          this.initSeedRelations(seed, micro.seeds)
        }
      }
    }
  }

  private initSeedRelations(seed: Seed, seeds: Record<string, Seed>) {
    if(seed.belongsTo) {
      for(const cross in seed.belongsTo) {
        const belongsTo = seed.belongsTo[cross]
        if(!belongsTo.selfSeed) belongsTo.selfSeed = seed

        if(!belongsTo.targetSeed) {
          const targetSeed = seeds[belongsTo.seedName]
          if(!targetSeed) throw new Error(`元数据【${belongsTo.seedName}】不存在`);

          belongsTo.targetSeed = targetSeed
        }
      }
    }

    if(seed.hasOne) {
      for(const cross in seed.hasOne) {
        const hasOne = seed.hasOne[cross]
        if(!hasOne.selfSeed) hasOne.selfSeed = seed

        if(!hasOne.targetSeed) {
          const targetSeed = seeds[hasOne.seedName]
          if(!targetSeed) throw new Error(`元数据【${hasOne.seedName}】不存在`);
          
          hasOne.targetSeed = targetSeed
        }
      }
    }

    if(seed.hasMany) {
      for(const cross in seed.hasMany) {
        const hasMany = seed.hasMany[cross]
        if(!hasMany.selfSeed) hasMany.selfSeed = seed

        if(!hasMany.targetSeed) {
          const targetSeed = seeds[hasMany.seedName]
          if(!targetSeed) throw new Error(`元数据【${hasMany.seedName}】不存在`);
          
          hasMany.targetSeed = targetSeed
        }
      }
    }

    if(seed.habtm) {
      for(const cross in seed.habtm) {
        const habtm = seed.habtm[cross]
        if(!habtm.selfSeed) habtm.selfSeed = seed

        if(!habtm.targetSeed) {
          const targetSeed = seeds[habtm.seedName]
          if(!targetSeed) throw new Error(`元数据【${habtm.seedName}】不存在`);
          
          habtm.targetSeed = targetSeed
        }

        if(!habtm.throughSeed) {
          if(!habtm.throughSeedName) throw Error(`请为【${seed.name}.${cross}】配置【throughSeedName】`)
          const throughSeed = seeds[habtm.throughSeedName];
          if(!throughSeed) throw new Error(`元数据【${habtm.throughSeedName}】不存在`);
          
          habtm.throughSeed = throughSeed;
        }
      }
    }
  }  

  private initGroups() {
    if(this.app.groups) return

    this.app.groups = {}
    const { homes, actions, seeds, name, label, comment, icon, color, size, hoverColor } = this.app
    const items = []
    if(homes) for(const homeName in homes) items.push(homeName)
    if(actions) for(const actionName in actions) items.push(actionName)
    if(seeds) {
      for(const seedName in seeds) {
        const seed = seeds[seedName]
        if(seed.type !== 'through' && !seed.hidden) items.push(seedName)
      }
    }
    this.app.groups[''] = { name, label, comment, icon, color, size, hoverColor, items }

    const micros = this.app.micros
    if(!micros) return
    
    for(const microName in micros) {
      const micro = micros[microName]
      const { homes, actions, seeds, label, comment, icon, color, size, hoverColor } = micro
      const items = []
      if(homes) for(const homeName in homes) items.push(`${microName}.${homeName}`)
      if(actions) for(const actionName in actions) items.push(`${microName}.${actionName}`)
      if(seeds) for(const seedName in seeds) items.push(`${microName}.${seedName}`)
      this.app.groups[microName] = { name: microName, label, comment, icon, color, size, hoverColor, items }
    }
  }

  private mergeSeeds(seeds: Record<string, Seed>, libSeeds: Record<string, Seed>, otherSeeds: Record<string, Seed>) {
    const tmp: string[] = []
    if(seeds) tmp.push(...Object.keys(seeds))
    if(libSeeds) tmp.push(...Object.keys(libSeeds))
    if(otherSeeds) tmp.push(...Object.keys(otherSeeds))
    if(tmp.length === 0) return void 0

    const ret: Record<string, Seed> = {}
    const seedNames = Array.from(new Set(tmp))
    for(const seedName of seedNames) {
      ret[seedName] = this.mergeSeed(seeds?.[seedName], libSeeds?.[seedName], otherSeeds?.[seedName])
    }

    return ret;
  }

  private mergeActions(actions: Record<string, Action>, libActions: Record<string, Action>, otherActions: Record<string, Action>) {
    const tmp: string[] = []
    if(actions) tmp.push(...Object.keys(actions))
    if(libActions) tmp.push(...Object.keys(libActions))
    if(otherActions) tmp.push(...Object.keys(otherActions))
    if(tmp.length === 0) return void 0

    const ret: Record<string, Action> = {}
    const actionNames = Array.from(new Set(tmp))
    for(const actionName of actionNames) {
      ret[actionName] = this.mergeAction(actions?.[actionName], libActions?.[actionName], otherActions?.[actionName])
    }

    return ret;
  }

  private mergeSeed(seed: Seed, libSeed: Seed, otherSeed: Seed) {
    const ret: Seed = {
      // 优先级 lib > other > 配置
      type: libSeed?.type || otherSeed?.type || seed?.type,
      nameColumn: libSeed?.nameColumn || otherSeed?.nameColumn || seed?.nameColumn,
      declares: this.merge(seed?.declares, libSeed?.declares, otherSeed?.declares),
      searches: this.merge(seed?.searches, libSeed?.searches, otherSeed?.searches),
      orders: this.merge(seed?.orders, libSeed?.orders, otherSeed?.orders),
      checkLogin: this.mergeBool(libSeed?.checkLogin, otherSeed?.checkLogin, seed?.checkLogin),
      checkAction: this.mergeBool(libSeed?.checkAction, otherSeed?.checkAction, seed?.checkAction),
      useKeyword: this.mergeBool(libSeed?.useKeyword, otherSeed?.useKeyword, seed?.useKeyword),

      // 优先级 配置 > other > lib
      // position: seed?.position || otherSeed?.position || libSeed?.position,
      mode: seed?.mode || otherSeed?.mode || libSeed?.mode,
      label: seed?.label || otherSeed?.label || libSeed?.label,
      comment: seed?.comment || otherSeed?.comment || libSeed?.comment,
      component: seed?.component || otherSeed?.component || libSeed?.component,
      layout: seed?.layout || otherSeed?.layout || libSeed?.layout,
      componentProps: seed?.componentProps || otherSeed?.componentProps || libSeed?.componentProps,
      popupProps: seed?.popupProps || otherSeed?.popupProps || libSeed?.popupProps,
      affix: this.mergeBool(seed?.affix, otherSeed?.affix, libSeed?.affix),
      hidden: this.mergeBool(seed?.hidden, otherSeed?.hidden, libSeed?.hidden),
      breadcrumb: this.mergeBool(seed?.breadcrumb, otherSeed?.breadcrumb, libSeed?.breadcrumb),
      noTagsView: this.mergeBool(seed?.noTagsView, otherSeed?.noTagsView, libSeed?.noTagsView),

      icon: seed?.icon || otherSeed?.icon || libSeed?.icon,
      color: seed?.color || otherSeed?.color || libSeed?.color,
      size: seed?.size || otherSeed?.size || libSeed?.size,
      hoverColor: seed?.hoverColor || otherSeed?.hoverColor || libSeed?.hoverColor,

      actions: this.mergeSeedActions(seed?.actions, libSeed?.actions, otherSeed?.actions),
      columns: this.mergeColumns(seed?.columns, otherSeed?.columns, libSeed?.columns),
      hasOne: this.mergeHasOne(seed, libSeed, otherSeed),
      hasMany: this.mergeHasMany(seed, libSeed, otherSeed),
      belongsTo: this.mergeBelongsTo(seed, libSeed, otherSeed),
      habtm: this.mergeHabtm(seed, libSeed, otherSeed),
      stats: this.mergeStats(seed, libSeed, otherSeed),
      groups: this.mergeGroups(seed, libSeed, otherSeed)
    }

    return ret
  }

  private merge(list: string[], libList: string[], otherList: string[]) {
    if(!list && !libList && !otherList) return void 0

    const ret: string[] = []
    if(libList) ret.push(...libList)
    if(otherList) ret.push(...otherList)
    if(list) ret.push(...list)

    return Array.from(new Set(ret));
  }

  private mergeBool(b1: boolean, b2: boolean, b3: boolean) {
    if(b1 !== void 0) return b1
    if(b2 !== void 0) return b2
    if(b3 !== void 0) return b3

    return void 0
  }

  private mergeSeedActions(actions: Record<string, Action>, libActions: Record<string, Action>, otherActions: Record<string, Action>) {
    const tmp: string[] = []
    if(actions) tmp.push(...Object.keys(actions))
    if(libActions) tmp.push(...Object.keys(libActions))
    if(otherActions) tmp.push(...Object.keys(otherActions))
    if(tmp.length === 0) return void 0

    const ret: Record<string, Action> = {}
    const actionNames = Array.from(new Set(tmp))
    for(const actionName of actionNames) {
      ret[actionName] = this.mergeAction(actions?.[actionName], libActions?.[actionName], otherActions?.[actionName])
    }    

    return ret
  }

  private mergeAction(action: Action, libAction: Action, otherAction: Action) {
    const ret: Action = {
      // 优先级 lib > other > 配置
      type: libAction?.type || otherAction?.type || action?.type,
      // toolbar: action?.toolbar || otherAction?.toolbar || libAction?.toolbar,
      // row: action?.row || otherAction?.row || libAction?.row,
      declare: libAction?.declare || otherAction?.declare || action?.declare,
      declareValue: libAction?.declareValue || otherAction?.declareValue || action?.declareValue,
      elements: this.mergeElements(action?.elements, libAction?.elements, otherAction?.elements),
      stats: libAction?.stats || otherAction?.stats || action?.stats,
    
      seedName: libAction?.seedName || otherAction?.seedName || action?.seedName,
      
      declares: this.merge(action?.declares, libAction?.declares, otherAction?.declares),
      searches: this.merge(action?.searches, libAction?.searches, otherAction?.searches),
      orders: this.merge(action?.orders, libAction?.orders, otherAction?.orders),
    
      checkLogin: this.mergeBool(libAction?.checkLogin, otherAction?.checkLogin, action?.checkLogin),
      checkAction: this.mergeBool(libAction?.checkAction, otherAction?.checkAction, action?.checkAction),
      useKeyword: this.mergeBool(libAction?.useKeyword, otherAction?.useKeyword, action?.useKeyword),
    
      // 优先级 配置 > other > lib
      // confirm: action?.confirm || otherAction?.confirm || libAction?.confirm,

      // position: action?.position || otherAction?.position || libAction?.position,
      mode: action?.mode || otherAction?.mode || libAction?.mode,
      label: action?.label || otherAction?.label || libAction?.label,
      comment: action?.comment || otherAction?.comment || libAction?.comment,
      component: action?.component || otherAction?.component || libAction?.component,
      layout: action?.layout || otherAction?.layout || libAction?.layout,
      componentProps: action?.componentProps || otherAction?.componentProps || libAction?.componentProps,
      popupProps: action?.popupProps || otherAction?.popupProps || libAction?.popupProps,
      affix: this.mergeBool(action?.affix, otherAction?.affix, libAction?.affix),
      hidden: this.mergeBool(action?.hidden, otherAction?.hidden, libAction?.hidden),
      breadcrumb: this.mergeBool(action?.breadcrumb, otherAction?.breadcrumb, libAction?.breadcrumb),
      noTagsView: this.mergeBool(action?.noTagsView, otherAction?.noTagsView, libAction?.noTagsView),

      icon: action?.icon || otherAction?.icon || libAction?.icon,
      color: action?.color || otherAction?.color || libAction?.color,
      size: action?.size || otherAction?.size || libAction?.size,
      hoverColor: action?.hoverColor || otherAction?.hoverColor || libAction?.hoverColor,      
    
      onClick: libAction?.onClick || otherAction?.onClick
    }

    return ret
  }

  private mergeElements(elements: Record<string, Element>, libElements: Record<string, Element>, otherElements: Record<string, Element>) {
    if(!elements && !libElements && !otherElements) return void 0

    const tmp: string[] = []
    if(elements) tmp.push(...Object.keys(elements))
    if(libElements) tmp.push(...Object.keys(libElements))
    if(otherElements) tmp.push(...Object.keys(otherElements))
    if(tmp.length === 0) return void 0

    const ret: Record<string, Element> = {}
    const declares = Array.from(new Set(tmp))
    for(const declare of declares) {
      // 优先级 配置 > other > lib
      ret[declare] = elements?.[declare] || otherElements?.[declare] || libElements?.[declare]
    }
    return ret;
  }

  private mergeColumns(columns: Record<string, Column>, libColumns: Record<string, Column>, otherColumns: Record<string, Column>) {
    if(!columns && !libColumns && !otherColumns) return void 0

    const tmp: string[] = []
    if(columns) tmp.push(...Object.keys(columns))
    if(libColumns) tmp.push(...Object.keys(libColumns))
    if(otherColumns) tmp.push(...Object.keys(otherColumns))
    if(tmp.length === 0) return void 0

    const ret: Record<string, Column> = {}
    const columnNames = Array.from(new Set(tmp))
    for(const columnName of columnNames) {
      // 优先级 lib > other > 配置
      ret[columnName] = libColumns?.[columnName] || otherColumns?.[columnName] || columns?.[columnName]
    }
    return ret;
  }

  private mergeHasOne(seed: Seed, libSeed: Seed, otherSeed: Seed) {
    const tmp: string[] = []
    if(seed?.hasOne) tmp.push(...Object.keys(seed.hasOne))
    if(libSeed?.hasOne) tmp.push(...Object.keys(libSeed?.hasOne))
    if(otherSeed?.hasOne) tmp.push(...Object.keys(otherSeed?.hasOne))
    if(tmp.length === 0) return void 0

    const ret: Record<string, HasOne> = {}
    const crosses = Array.from(new Set(tmp))
    for(const cross of crosses) {
      ret[cross] = libSeed?.hasOne?.[cross] || otherSeed?.hasOne?.[cross] || seed?.hasOne?.[cross]
    }

    return ret
  }

  private mergeHasMany(seed: Seed, libSeed: Seed, otherSeed: Seed) {
    const tmp: string[] = []
    if(seed?.hasMany) tmp.push(...Object.keys(seed.hasMany))
    if(libSeed?.hasMany) tmp.push(...Object.keys(libSeed?.hasMany))
    if(otherSeed?.hasMany) tmp.push(...Object.keys(otherSeed?.hasMany))
    if(tmp.length === 0) return void 0

    const ret: Record<string, HasMany> = {}
    const crosses = Array.from(new Set(tmp))
    for(const cross of crosses) {
      ret[cross] = libSeed?.hasMany?.[cross] || otherSeed?.hasMany?.[cross] || seed?.hasMany?.[cross]
    }

    return ret
  }

  private mergeBelongsTo(seed: Seed, libSeed: Seed, otherSeed: Seed) {
    const tmp: string[] = []
    if(seed?.belongsTo) tmp.push(...Object.keys(seed.belongsTo))
    if(libSeed?.belongsTo) tmp.push(...Object.keys(libSeed?.belongsTo))
    if(otherSeed?.belongsTo) tmp.push(...Object.keys(otherSeed?.belongsTo))
    if(tmp.length === 0) return void 0

    const ret: Record<string, HasMany> = {}
    const crosses = Array.from(new Set(tmp))
    for(const cross of crosses) {
      ret[cross] = libSeed?.belongsTo?.[cross] || otherSeed?.belongsTo?.[cross] || seed?.belongsTo?.[cross]
    }

    return ret
  }

  private mergeHabtm(seed: Seed, libSeed: Seed, otherSeed: Seed) {
    const tmp: string[] = []
    if(seed?.habtm) tmp.push(...Object.keys(seed.habtm))
    if(libSeed?.habtm) tmp.push(...Object.keys(libSeed?.habtm))
    if(otherSeed?.habtm) tmp.push(...Object.keys(otherSeed?.habtm))
    if(tmp.length === 0) return void 0

    const ret: Record<string, Habtm> = {}
    const crosses = Array.from(new Set(tmp))
    for(const cross of crosses) {
      ret[cross] = libSeed?.habtm?.[cross] || otherSeed?.habtm?.[cross] || seed?.habtm?.[cross]
    }

    return ret
  }
  
  private mergeStats(seed: Seed, libSeed: Seed, otherSeed: Seed) {
    const tmp: string[] = []
    if(seed?.stats) tmp.push(...Object.keys(seed.stats))
    if(libSeed?.stats) tmp.push(...Object.keys(libSeed?.stats))
    if(otherSeed?.stats) tmp.push(...Object.keys(otherSeed?.stats))
    if(tmp.length === 0) return void 0

    const ret: Record<string, Stat> = {}
    const statNames = Array.from(new Set(tmp))
    for(const statName of statNames) {
      ret[statName] = libSeed?.stats?.[statName] || otherSeed?.stats?.[statName] || seed?.stats?.[statName]
    }

    return ret
  }
  
  private mergeGroups(seed: Seed, libSeed: Seed, otherSeed: Seed) {
    const tmp: string[] = []
    if(seed?.groups) tmp.push(...Object.keys(seed.groups))
    if(libSeed?.groups) tmp.push(...Object.keys(libSeed?.groups))
    if(otherSeed?.groups) tmp.push(...Object.keys(otherSeed?.groups))
    if(tmp.length === 0) return void 0

    const ret: Record<string, Group> = {}
    const groupNames = Array.from(new Set(tmp))
    for(const groupName of groupNames) {
      ret[groupName] = libSeed?.groups?.[groupName] || otherSeed?.groups?.[groupName] || seed?.groups?.[groupName]
    }

    return ret
  }
}